package org.eclipse.iota.mapper;

import org.eclipse.iota.model.Addition;
import org.eclipse.iota.model.BinaryOperation;
import org.eclipse.iota.model.Constant;
import org.eclipse.iota.model.Division;
import org.eclipse.iota.model.Exponentiation;
import org.eclipse.iota.model.Expression;
import org.eclipse.iota.model.MultiOperation;
import org.eclipse.iota.model.Multiplication;
import org.eclipse.iota.model.Negation;
import org.eclipse.iota.model.Operation;
import org.eclipse.iota.model.Reference;
import org.eclipse.iota.model.UnaryOperation;

/**
 * Abstract expression mapper implementation.
 * @author Christian Krause
 * @generated NOT
 * @param <T> Mapping target type.
 */
public abstract class AbstractExpressionMapper<T> implements IotaExpressionMapper<T> {
	
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.iota.mapper.IotaExpressionMapper#map(org.eclipse.iota.core.Expression)
	 */
	@Override
	public T map(Expression expression) {
		if (expression instanceof Constant) {
			return map((Constant) expression);
		}
		if (expression instanceof Reference) {
			return map((Reference) expression);
		}
		if (expression instanceof Operation) {
			return map((Operation) expression);
		}		
		return null;
	}
	
	/**
	 * Map a constant.
	 * @param constant Constant.
	 * @return Mapped element.
	 */
	protected abstract T map(Constant constant);

	/**
	 * Map a reference.
	 * @param reference Reference.
	 * @return Mapped element.
	 */
	protected abstract T map(Reference reference);

	/**
	 * Map an operation.
	 * @param operation Operation.
	 * @return Mapped element.
	 */
	protected T map(Operation operation) {
		if (operation instanceof UnaryOperation) {
			return map((UnaryOperation) operation);
		}
		if (operation instanceof BinaryOperation) {
			return map((BinaryOperation) operation);
		}
		if (operation instanceof MultiOperation) {
			return map((MultiOperation) operation);
		}
		return null;
	}
	
	/**
	 * Map a unary operation.
	 * @param Unary operation.
	 * @return Mapped element.
	 */
	protected T map(UnaryOperation operation) {
		return performOperation(operation.getClass(), map(operation.getOperand()), null);
	}
	
	/**
	 * Map a binary operation.
	 * @param Binary operation.
	 * @return Mapped element.
	 */
	protected T map(BinaryOperation operation) {
		return performOperation(operation.getClass(), map(operation.getLeft()), map(operation.getRight()));
	}
	
	/**
	 * Map a multi-operation.
	 * @param Multi-operation.
	 * @return Mapped element.
	 */
	protected T map(MultiOperation operation) {
		Class<? extends MultiOperation> type = operation.getClass();
		if (operation.getOperands().isEmpty()) {
			return map(operation.createNeutralElement());
		}
		T result = map(operation.getOperands().get(0));
		for (int i=1; i<operation.getOperands().size(); i++) {
			result = performOperation(type, result, map(operation.getOperands().get(i)));
		}
		return result;
	}
	
	/**
	 * Perform an arithmetical operation on two mapped arguments.
	 * Subclasses may override this method or the specific arithmetical methods.
	 * 
	 * @param type Type of the arithmetical operation.
	 * @param a1 First argument.
	 * @param a2 Second argument.
	 * @return The result.
	 */
	protected T performOperation(Class<? extends Operation> type, T a1, T a2) {
		if (type==Addition.class) return add(a1,a2);
		if (type==Multiplication.class) return multiply(a1,a2);
		if (type==Division.class) return divide(a1,a2);
		if (type==Exponentiation.class) return pow(a1,a2);
		if (type==Negation.class) return negate(a1);
		return null;
	}
	

	// ----- Arithmetical methods ------ //
	
	/**
	 * Negate a mapped element.
	 * @param argument Argument.
	 * @return Negation result.
	 */
	protected T negate(T argument) {
		return null;
	}

	/**
	 * Add to mapped elements.
	 * @param a1 Element 1.
	 * @param a2 Element 2.
	 * @return Addition result.
	 */
	protected T add(T a1, T a2) {
		return null;
	}

	/**
	 * Multiply to mapped elements.
	 * @param a1 Element 1.
	 * @param a2 Element 2.
	 * @return Addition result.
	 */
	protected T multiply(T a1, T a2) {
		return null;
	}

	/**
	 * Divide to mapped elements.
	 * @param a1 Element 1.
	 * @param a2 Element 2.
	 * @return Division result.
	 */
	protected T divide(T a1, T a2) {
		return null;
	}

	/**
	 * Take the power of mapped elements.
	 * @param a1 Element 1.
	 * @param a2 Element 2.
	 * @return Exponentiation result.
	 */
	protected T pow(T a1, T a2) {
		return null;
	}

}
